/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "border_option_napi.h"
#include "imageknife_internal.h"
#include "napi/native_api.h"
#include "imageknife_option_napi.h"
#include "log.h"
#include "imageknife.h"
#include "napi_utils.h"
#include "onload_callback_napi.h"
#include "transformation/transformation_factory.h"
#include "transformation/transformation_option_napi.h"
#include "utils/napi_parser.h"
#include <cstdint>
#include <native_drawing/drawing_color_filter.h>

namespace ImageKnifePro {

ImageKnifeOption::~ImageKnifeOption()
{
    if (onFinalize != nullptr) {
        onFinalize(customData);
    }

    if (context.resourceManager != nullptr) {
        OH_ResourceManager_ReleaseNativeResourceManager(context.resourceManager);
        if (context.config != nullptr) {
            OH_ResourceManager_ReleaseConfiguration(context.config);
            delete context.config;
            context.config = nullptr;
        }
    }
}

void ImageKnifeOptionNapi::ParseHttpOption(napi_env env, napi_value jsObject, HttpRequestOption &httpOptions)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "caPath");
    if (value != nullptr) {
        httpOptions.caPath = NapiParser::ParseString(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "connectTimeout");
    if (value != nullptr) {
        httpOptions.connectTimeout = NapiParser::ParseInt(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "readTimeout");
    if (value != nullptr) {
        httpOptions.readTimeout = NapiParser::ParseInt(env, value);
    }
}

void ImageKnifeOptionNapi::ParseResizeableOption(napi_env env, napi_value jsObject)
{
    napi_value resizeableOption = NapiParser::ParseProperty(env, jsObject, "resizeable");
    if (resizeableOption == nullptr) {
        return;
    }

    resizeable = std::make_shared<ResizableOption>();
    napi_value value = NapiParser::ParseProperty(env, resizeableOption, "left");
    if (value != nullptr) {
        resizeable->left = NapiParser::ParseFloat(env, value);
    }

    value = NapiParser::ParseProperty(env, resizeableOption, "top");
    if (value != nullptr) {
        resizeable->top = NapiParser::ParseFloat(env, value);
    }

    value = NapiParser::ParseProperty(env, resizeableOption, "right");
    if (value != nullptr) {
        resizeable->right = NapiParser::ParseFloat(env, value);
    }

    value = NapiParser::ParseProperty(env, resizeableOption, "bottom");
    if (value != nullptr) {
        resizeable->bottom = NapiParser::ParseFloat(env, value);
    }
}

void ImageKnifeOptionNapi::ParseColorFilter(napi_env env, napi_value jsObject)
{
    uint32_t color = 0;
    OH_Drawing_BlendMode mode = BLEND_MODE_CLEAR;

    napi_value value = NapiParser::ParseProperty(env, jsObject, "color");
    if (value != nullptr) {
        const int shiftBits24 = 24;
        const int shiftBits16 = 16;
        const int shiftBits8 = 8;
        int alpha = 0;
        int red = 0;
        int green = 0;
        int blue = 0;

        napi_value subValue = NapiParser::ParseProperty(env, value, "alpha");
        if (subValue != nullptr) {
            napi_get_value_int32(env, subValue, &alpha);
        }
        subValue = NapiParser::ParseProperty(env, value, "red");
        if (subValue != nullptr) {
            napi_get_value_int32(env, subValue, &red);
        }
        subValue = NapiParser::ParseProperty(env, value, "green");
        if (subValue != nullptr) {
            napi_get_value_int32(env, subValue, &green);
        }
        subValue = NapiParser::ParseProperty(env, value, "blue");
        if (subValue != nullptr) {
            napi_get_value_int32(env, subValue, &blue);
        }
        color = (alpha << shiftBits24) | (red << shiftBits16) | (green << shiftBits8) | blue;
    }

    value = NapiParser::ParseProperty(env, jsObject, "mode");
    if (value != nullptr) {
        mode = static_cast<OH_Drawing_BlendMode>(NapiParser::ParseInt(env, value));
    }
    OH_Drawing_ColorFilter *mColorFilter = OH_Drawing_ColorFilterCreateBlendMode(color, mode);
    colorFilter = std::make_shared<ImageKnifeColorFilter>(mColorFilter);
}

std::shared_ptr<Transformation> ParseTransformationOption(napi_env env, napi_value value)
{
    auto option = std::make_shared<TransformationOptionNapi>(env, value);
    if (option->type == TransformationType::CUSTOM_TRANSFORMATION) {
        return ImageKnifeInternal::GetInstance().GetRegisterTransformation(option->customTransformation);
    } else if (option->type == TransformationType::CUSTOM_TRANSFORMATION_CONSTRUCTOR) {
        auto constructor = ImageKnifeInternal::GetInstance()
            .GetRegisterTransformationConstructor(option->customTransformation);
        if (constructor != nullptr) {
            return constructor->CreateTransformation(option);
        }
    } else if (option->type != TransformationType::UNKNOWN) {
        return TransformationFactory::CreateTransformation(option);
    }

    return nullptr;
}

void ImageKnifeOptionNapi::ParseTransformation(napi_env env, napi_value jsObject)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "transformationOption");
    if (value != nullptr) {
        transformation = ParseTransformationOption(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "multiTransformationOption");
    if (value != nullptr) {
        uint32_t length = 0;
        napi_status status = napi_get_array_length(env, value, &length);
        if (status != napi_ok) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "ParseTransformation:napi_get_array_length failed: %{public}d", status);
            return;
        }
        napi_value element;
        std::vector<std::shared_ptr<Transformation>> transformationVec;
        for (int i = 0; i < length; i++) {
            napi_get_element(env, value, i, &element);
            auto transformation = ParseTransformationOption(env, element);
            if (transformation != nullptr) {
                transformationVec.push_back(transformation);
            }
        }
        if (!transformationVec.empty()) {
            multiTransformation = std::make_shared<MultiTransformation>(transformationVec);
        }
    }
}

void ImageKnifeOptionNapi::ParseFallbackUrls(napi_env env, napi_value jsFallbackUrls)
{
    napi_valuetype valueType;
    napi_typeof(env, jsFallbackUrls, &valueType);
    if (valueType == napi_undefined) {
        return;
    }
    uint32_t length = 0;
    napi_status status = napi_get_array_length(env, jsFallbackUrls, &length);
    if (status != napi_ok) {
        return;
    }
    napi_value element;
    for (int i = 0; i < length; i++) {
        napi_get_element(env, jsFallbackUrls, i, &element);
        std::string fallbackUrl = NapiParser::ParseString(env, element);
        if (!fallbackUrl.empty()) {
            fallbackUrls.push_back(fallbackUrl);
        }
    }
}

void ImageKnifeOptionNapi::ParseHttpHeaders(napi_env env, napi_value jsHeaders,
                                            std::multimap<std::string, std::string> &headers)
{
    uint32_t length = 0;
    napi_status status = napi_get_array_length(env, jsHeaders, &length);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "ParseHttpHeaders:napi_get_array_length failed");
        return;
    }

    for (int i = 0; i < length; i++) {
        napi_handle_scope scope;
        napi_value element;
        napi_value key;
        napi_value value;
        napi_valuetype type;
        napi_open_handle_scope(env, &scope);
        napi_get_element(env, jsHeaders, i, &element);

        napi_get_named_property(env, element, "key", &key);
        std::string keyStr = NapiParser::ParseString(env, key);

        napi_get_named_property(env, element, "value", &value);
        napi_typeof(env, value, &type);
        if (type == napi_string) {
            headers.insert(std::make_pair(keyStr, NapiParser::ParseString(env, value)));
        } else if (type == napi_object) {
            uint32_t valueArrayLength = 0;
            napi_get_array_length(env, value, &valueArrayLength);
            for (int j = 0; j < valueArrayLength; j++) {
                napi_value valueElement;
                napi_get_element(env, value, j, &valueElement);
                headers.insert(std::make_pair(keyStr, NapiParser::ParseString(env, valueElement)));
            }
        }

        napi_close_handle_scope(env, scope);
    }
}

void ImageKnifeOptionNapi::ParseCallbackProperty(napi_env env, napi_value jsObject)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "onLoadListener");
    if (value != nullptr) {
        onLoadListener = std::make_shared<OnLoadCallBackNapi>(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "onComplete");
    if (value != nullptr) {
        napi_create_reference(env, value, 1, &onComplete_);
        onComplete = [this](EventImage eventImage) {
            this->CallJsOnComplete(eventImage);
        };
    }

    value = NapiParser::ParseProperty(env, jsObject, "progressListener");
    if (value != nullptr) {
        ParseProgressListener(env, value);
    }
}

void ImageKnifeOptionNapi::ParseImageProperty(napi_env env, napi_value jsObject)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "loadSrc");
    if (value != nullptr) {
        NapiParser::ParseImageSource(loadSrc, env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "placeholderSrc");
    if (value != nullptr) {
        NapiParser::ParseImageSource(placeholderSrc, env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "thumbnailSrc");
    if (value != nullptr) {
        NapiParser::ParseImageSource(thumbnailSrc, env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "errorSrc");
    if (value != nullptr) {
        NapiParser::ParseImageSource(errorSrc, env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "signature");
    if (value != nullptr) {
        signature = NapiParser::ParseString(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "pixelName");
    if (value != nullptr) {
        pixelName = NapiParser::ParseString(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "priority");
    if (value != nullptr) {
        priority = static_cast<Priority>(NapiParser::ParseInt(env, value));
    }

    value = NapiParser::ParseProperty(env, jsObject, "objectFit");
    if (value != nullptr) {
        objectFit = ParseImagFit(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "placeholderObjectFit");
    if (value != nullptr) {
        placeholderObjectFit = ParseImagFit(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "thumbnailObjectFit");
    if (value != nullptr) {
        thumbnailObjectFit = ParseImagFit(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "errorObjectFit");
    if (value != nullptr) {
        errorObjectFit = ParseImagFit(env, value);
    }
}

void ImageKnifeOptionNapi::ParseLoadProperty(napi_env env, napi_value jsObject)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "headerOption");
    if (value != nullptr) {
        ParseHttpHeaders(env, value, httpHeaders);
    }

    value = NapiParser::ParseProperty(env, jsObject, "httpOption");
    if (value != nullptr) {
        ParseHttpOption(env, value, httpOption);
    }

    value = NapiParser::ParseProperty(env, jsObject, "crc32");
    if (value != nullptr) {
        crc32 = NapiParser::ParseUint32(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "fallbackUrls");
    if (value != nullptr) {
        ParseFallbackUrls(env, value);
    }
}

ImageKnifeOptionNapi::ImageKnifeOptionNapi(napi_env env, napi_value &jsObject)
{
    ImageKnifeTrace trace("ImageKnifeOptionNapi");
    trace.StartSyncTrace();

    env_ = env;
    napi_create_reference(env, jsObject, 1, &jsThis_);
    imageLoader =  ImageKnifeInternal::GetInstance().GetDefaultImageKnifeLoader();
    ParseImageProperty(env, jsObject);
    ParseCallbackProperty(env, jsObject);
    ParseTransformation(env, jsObject);
    ParseLoadProperty(env, jsObject);
    ParseResizeableOption(env, jsObject);
    ParsePixelRelateProperty(env, jsObject);

    napi_value value = NapiParser::ParseProperty(env, jsObject, "imageKnifeLoader");
    if (value != nullptr) {
        imageLoader = ImageKnifeInternal::GetInstance().GetRegisterLoader(NapiParser::ParseString(env, value));
    }

    value = NapiParser::ParseProperty(env, jsObject, "fileCacheName");
    if (value != nullptr) {
        fileCacheName = NapiParser::ParseString(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "writeCacheStrategy");
    if (value != nullptr) {
        writeCacheStrategy = static_cast<CacheStrategy>(NapiParser::ParseInt(env, value));
    }

    value = NapiParser::ParseProperty(env, jsObject, "onlyRetrieveFromCache");
    if (value != nullptr) {
        onlyRetrieveFromCache = NapiParser::ParseBoolean(env, value, onlyRetrieveFromCache);
    }

    value = NapiParser::ParseProperty(env, jsObject, "border");
    if (value != nullptr) {
        border = std::make_shared<BorderOptionNapi>(env, value);
    }

    // 优先从自身ets层的option.context上解析，再使用兜底默认值
    value = NapiParser::ParseProperty(env, jsObject, "context");
    if (value != nullptr) {
        ParseContext(env, value);
    }
}

void ImageKnifeOptionNapi::ParsePixelRelateProperty(napi_env env, napi_value jsObject)
{
    napi_value value = NapiParser::ParseProperty(env, jsObject, "drawingColorFilter");
    if (value != nullptr) {
        ParseColorFilter(env, value);
    }

    value = NapiParser::ParseProperty(env, jsObject, "downSampling");
    if (value != nullptr) {
        downSampling = static_cast<DownSamplingStrategy>(NapiParser::ParseInt(env, value));
    }

    value = NapiParser::ParseProperty(env, jsObject, "orientation");
    if (value != nullptr) {
        orientation = static_cast<Orientation>(NapiParser::ParseInt(env, value));
    }

    value = NapiParser::ParseProperty(env, jsObject, "animationDecodeMode");
    if (value != nullptr) {
        animationDecodeMode = static_cast<AnimationDecodeMode>(NapiParser::ParseInt(env, value));
    }
}

void ImageKnifeOptionNapi::ParseContext(napi_env env, napi_value value)
{
    // 已解析到用户设置的context，不再考虑默认context
    if (context.resourceManager != nullptr) {
        return;
    }

    napi_value resourceManager = NapiParser::ParseProperty(env, value, "resourceManager");
    if (resourceManager != nullptr) {
        context.resourceManager = OH_ResourceManager_InitNativeResourceManager(env, resourceManager);
        IMAGE_KNIFE_CHECK(context.resourceManager == nullptr);
        context.config = new ResourceManager_Configuration;
        auto errorCode = OH_ResourceManager_GetConfiguration(context.resourceManager, context.config);
        if (errorCode != ResourceManager_ErrorCode::SUCCESS) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "Get Context.config Failed, error code: %{public}d", errorCode);
            delete context.config;
            context.config = nullptr;
        }
    }
}

void ImageKnifeOptionNapi::ParseProgressListener(napi_env env, napi_value value)
{
    napi_status status;
    napi_valuetype type;
    napi_typeof(env, value, &type);
    if (type != napi_undefined) {
        napi_create_reference(env, value, 1, &progressListener_);
        progressListener = [this](double progress) {
            auto func = [this, progress](void *) {
                this->CallJsProgressListener(progress);
            };
            TaskWorker::GetInstance()->ToMainThread(func, nullptr);
        };
    }
}

ArkUI_ObjectFit ImageKnifeOptionNapi::ParseImagFit(napi_env env, napi_value &value)
{
    // 默认为Contain与js版本的ImageKnife保持一致，与系统Image不一致
    int num = ARKUI_OBJECT_FIT_CONTAIN;
    napi_get_value_int32(env, value, &num);
    // js 侧的枚举 ImageFit 与侧对应的ArkUI_ObjectFit 不一致，需要映射
    ArkUI_ObjectFit jsEnum[] = {
        ARKUI_OBJECT_FIT_FILL,  // js侧的该枚举值为0，而不是4
        ARKUI_OBJECT_FIT_CONTAIN,
        ARKUI_OBJECT_FIT_COVER,
        ARKUI_OBJECT_FIT_AUTO,
        ARKUI_OBJECT_FIT_CONTAIN, // js侧的枚举值没有4，为异常值使用默认Contain
        ARKUI_OBJECT_FIT_NONE,  // js侧的该枚举值为5，而不是6
        ARKUI_OBJECT_FIT_SCALE_DOWN // js侧的该枚举值为6，而不是5
    };

    // js后续的枚举值从TOP_START 开始显式定义枚举值, 后续的枚举值比C侧大1
    if (num > sizeof(jsEnum) / sizeof(jsEnum[0])) {
        return static_cast<ArkUI_ObjectFit>(num - 1);
    } else {
        return jsEnum[num];
    }
}

void ImageKnifeOptionNapi::CallJsProgressListener(double progress)
{
    napi_handle_scope scope;
    napi_open_handle_scope(env_, &scope);

    napi_status status;
    napi_value thisValue;
    napi_value jsFunc;
    napi_get_reference_value(env_, jsThis_, &thisValue);
    napi_get_reference_value(env_, progressListener_, &jsFunc);

    napi_value number;
    napi_create_double(env_, progress, &number);
    status = napi_call_function(env_, nullptr, jsFunc, 1, &number, nullptr);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "CallJsProgressListener failed, napi status: %{public}d", status);
    }
    napi_close_handle_scope(env_, scope);
}

void ImageKnifeOptionNapi::CallJsOnComplete(EventImage &eventImage)
{
    napi_handle_scope scope;
    napi_open_handle_scope(env_, &scope);

    napi_status status;
    napi_value thisValue;
    napi_value jsFunc;
    napi_value eventImageValue;
    napi_value number;

    napi_create_object(env_, &eventImageValue);

    napi_create_int32(env_, eventImage.loadingStatus, &number);
    napi_set_named_property(env_, eventImageValue, "loadingStatus", number);

    napi_create_double(env_, eventImage.width, &number);
    napi_set_named_property(env_, eventImageValue, "width", number);

    napi_create_double(env_, eventImage.height, &number);
    napi_set_named_property(env_, eventImageValue, "height", number);

    napi_create_double(env_, eventImage.componentWidth, &number);
    napi_set_named_property(env_, eventImageValue, "componentWidth", number);

    napi_create_double(env_, eventImage.componentHeight, &number);
    napi_set_named_property(env_, eventImageValue, "componentHeight", number);

    napi_create_double(env_, eventImage.contentOffsetX, &number);
    napi_set_named_property(env_, eventImageValue, "contentOffsetX", number);

    napi_create_double(env_, eventImage.contentOffsetY, &number);
    napi_set_named_property(env_, eventImageValue, "contentOffsetY", number);

    napi_create_double(env_, eventImage.contentWidth, &number);
    napi_set_named_property(env_, eventImageValue, "contentWidth", number);

    napi_create_double(env_, eventImage.contentHeight, &number);
    napi_set_named_property(env_, eventImageValue, "contentHeight", number);

    napi_get_reference_value(env_, jsThis_, &thisValue);
    napi_get_reference_value(env_, onComplete_, &jsFunc);
    status = napi_call_function(env_, thisValue, jsFunc, 1, &eventImageValue, nullptr);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "CallJsOnComplete failed, napi status: %{public}d", status);
    }
    napi_close_handle_scope(env_, scope);
}

ImageKnifeOptionNapi::~ImageKnifeOptionNapi()
{
    NapiUtils::DeleteReferences(env_, {jsThis_, onComplete_, progressListener_});
}

} // end of namespace